package com.hqd.ch03.v49.core.annotation;


import com.hqd.ch03.v49.web.utils.ConcurrentReferenceHashMap;

import java.lang.annotation.Annotation;
import java.util.*;

final class AnnotationTypeMappings {


    private static final Map<AnnotationFilter, Cache> standardRepeatablesCache = new ConcurrentReferenceHashMap<>();

    private static final Map<AnnotationFilter, Cache> noRepeatablesCache = new ConcurrentReferenceHashMap<>();


    private final RepeatableContainers repeatableContainers;

    private final AnnotationFilter filter;

    private final List<AnnotationTypeMapping> mappings;


    private AnnotationTypeMappings(RepeatableContainers repeatableContainers,
                                   AnnotationFilter filter, Class<? extends Annotation> annotationType,
                                   Set<Class<? extends Annotation>> visitedAnnotationTypes) {

        this.repeatableContainers = repeatableContainers;
        this.filter = filter;
        this.mappings = new ArrayList<>();
        addAllMappings(annotationType, visitedAnnotationTypes);
        this.mappings.forEach(AnnotationTypeMapping::afterAllMappingsSet);
    }

    static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) {
        return forAnnotationType(annotationType, new HashSet<>());
    }

    static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
                                                    Set<Class<? extends Annotation>> visitedAnnotationTypes) {

        return forAnnotationType(annotationType, RepeatableContainers.standardRepeatables(),
                AnnotationFilter.PLAIN, visitedAnnotationTypes);
    }

    static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
                                                    RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {

        return forAnnotationType(annotationType, repeatableContainers, annotationFilter, new HashSet<>());
    }

    private static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType,
                                                            RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter,
                                                            Set<Class<? extends Annotation>> visitedAnnotationTypes) {

        if (repeatableContainers == RepeatableContainers.standardRepeatables()) {
            return standardRepeatablesCache.computeIfAbsent(annotationFilter,
                    key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);
        }
        if (repeatableContainers == RepeatableContainers.none()) {
            return noRepeatablesCache.computeIfAbsent(annotationFilter,
                    key -> new Cache(repeatableContainers, key)).get(annotationType, visitedAnnotationTypes);
        }
        return new AnnotationTypeMappings(repeatableContainers, annotationFilter, annotationType, visitedAnnotationTypes);
    }

    static void clearCache() {
        standardRepeatablesCache.clear();
        noRepeatablesCache.clear();
    }

    private void addAllMappings(Class<? extends Annotation> annotationType,
                                Set<Class<? extends Annotation>> visitedAnnotationTypes) {
        /**
         * 广度优先遍历，或者说是层级遍历？
         */
        Deque<AnnotationTypeMapping> queue = new ArrayDeque<>();
        /**
         * 将节点添加到队列
         */
        addIfPossible(queue, null, annotationType, null, visitedAnnotationTypes);
        while (!queue.isEmpty()) {
            /**
             * 出队
             */
            AnnotationTypeMapping mapping = queue.removeFirst();
            this.mappings.add(mapping);
            /**
             * 继续遍历子节点
             */
            addMetaAnnotationsToQueue(queue, mapping);
        }
    }

    private void addMetaAnnotationsToQueue(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source) {
        /**
         * 获取节点上的注解
         */
        Annotation[] metaAnnotations = AnnotationsScanner.getDeclaredAnnotations(source.getAnnotationType(), false);
        for (Annotation metaAnnotation : metaAnnotations) {
            if (!isMappable(source, metaAnnotation)) {
                continue;
            }
            /**
             * 可重复注解
             */
            Annotation[] repeatedAnnotations = this.repeatableContainers.findRepeatedAnnotations(metaAnnotation);
            if (repeatedAnnotations != null) {
                for (Annotation repeatedAnnotation : repeatedAnnotations) {
                    if (!isMappable(source, repeatedAnnotation)) {
                        continue;
                    }
                    addIfPossible(queue, source, repeatedAnnotation);
                }
            } else {
                addIfPossible(queue, source, metaAnnotation);
            }
        }
    }

    private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source, Annotation ann) {
        addIfPossible(queue, source, ann.annotationType(), ann, new HashSet<>());
    }

    private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping source,
                               Class<? extends Annotation> annotationType, Annotation ann,
                               Set<Class<? extends Annotation>> visitedAnnotationTypes) {

        try {
            queue.addLast(new AnnotationTypeMapping(source, annotationType, ann, visitedAnnotationTypes));
        } catch (Exception ex) {
            AnnotationUtils.rethrowAnnotationConfigurationException(ex);
        }
    }

    private boolean isMappable(AnnotationTypeMapping source, Annotation metaAnnotation) {
        return (metaAnnotation != null && !this.filter.matches(metaAnnotation) &&
                !AnnotationFilter.PLAIN.matches(source.getAnnotationType()) &&
                !isAlreadyMapped(source, metaAnnotation));
    }

    private boolean isAlreadyMapped(AnnotationTypeMapping source, Annotation metaAnnotation) {
        Class<? extends Annotation> annotationType = metaAnnotation.annotationType();
        AnnotationTypeMapping mapping = source;
        while (mapping != null) {
            if (mapping.getAnnotationType() == annotationType) {
                return true;
            }
            mapping = mapping.getSource();
        }
        return false;
    }

    int size() {
        return this.mappings.size();
    }

    AnnotationTypeMapping get(int index) {
        return this.mappings.get(index);
    }

    private static class Cache {

        private final RepeatableContainers repeatableContainers;

        private final AnnotationFilter filter;

        private final Map<Class<? extends Annotation>, AnnotationTypeMappings> mappings;

        Cache(RepeatableContainers repeatableContainers, AnnotationFilter filter) {
            this.repeatableContainers = repeatableContainers;
            this.filter = filter;
            this.mappings = new ConcurrentReferenceHashMap<>();
        }

        AnnotationTypeMappings get(Class<? extends Annotation> annotationType,
                                   Set<Class<? extends Annotation>> visitedAnnotationTypes) {

            return this.mappings.computeIfAbsent(annotationType, key -> createMappings(key, visitedAnnotationTypes));
        }

        private AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType,
                                                      Set<Class<? extends Annotation>> visitedAnnotationTypes) {

            return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType, visitedAnnotationTypes);
        }
    }

}
